package jmine.tec.hydra.codec.impl;

import java.io.IOException;
import java.io.InputStream;
import java.util.Map;

import jmine.tec.hydra.HydraController;
import jmine.tec.hydra.HydraMessages;
import jmine.tec.hydra.codec.Encoder;
import jmine.tec.hydra.codec.EncoderData;
import jmine.tec.hydra.exception.HydraException;
import jmine.tec.hydra.script.EncodeResult;
import jmine.tec.hydra.script.EncodeScriptContext;
import jmine.tec.hydra.script.RenderMessageScriptContext;
import jmine.tec.hydra.script.impl.RenderResultImpl;
import jmine.tec.script.ScriptExecutorException;
import jmine.tec.script.persistent.domain.Script;

/**
 * Implementação de encoder via scripts de encoding e rendering
 * 
 * @author piercio
 */
public class ScriptEncoder implements Encoder {

    private final HydraController controller;

    private final Script encodeScript;

    private final Script template;

    /**
     * Construtor
     * 
     * @param controller controller
     * @param script script que popula o contexto do script de renderização
     * @param template script de renderização
     */
    public ScriptEncoder(HydraController controller, Script script, Script template) {
        this.controller = controller;
        this.encodeScript = script;
        this.template = template;
    }

    /**
     * Codifica o objeto em uma mensagem.
     * 
     * @param source o conteúdo com as informações necessárias para a criação da mensagem
     * @return Mensagem codificada
     */
    public EncoderData encode(Object source) {
        EncodeResult encodeResult = this.runScript(source, this.encodeScript);

        Map<String, Object> rendererParameters = encodeResult.getRendererParameters();
        if (!rendererParameters.containsKey(Encoder.IDENTIFICATION)) {
            rendererParameters.put(Encoder.IDENTIFICATION, this.controller.createIdentification());
        }

        RenderResultImpl renderResult = this.runTemplate(rendererParameters);
        InputStream messageStream;
        try {
            messageStream = renderResult.getStream().getAsInputStream();
        } catch (IOException e) {
            throw new HydraException(HydraMessages.VELOCITY_TEMPLATE.create(), e);
        }

        if (renderResult.getScriptLogger() == null) {
            return new EncoderData(messageStream, (String) rendererParameters.get(Encoder.IDENTIFICATION), encodeResult.getScriptLogger());
        }

        return new EncoderData(messageStream, (String) rendererParameters.get(Encoder.IDENTIFICATION), encodeResult.getScriptLogger(),
                renderResult.getScriptLogger());
    }

    /**
     * Executa o script de codificação.
     * 
     * @param source o objeto que será utilizado pelo script.
     * @param script o script de encoding.
     * @return mapa de parâmetros para o script de renderização.
     */
    private EncodeResult runScript(Object source, Script script) {
        try {
            return script.createContext(EncodeScriptContext.class).prepare(source).dispatchExecution();
        } catch (ScriptExecutorException e) {
            throw new HydraException(HydraMessages.BSH_ENCODE.create(script.getMnemonico()), e);
        }
    }

    /**
     * Executa o script de renderização e devolve a mensagem gerada.
     * 
     * @param parameters os parâmetros para o script de renderização de mensagem.
     * @return a mensagem gerada.
     */
    private RenderResultImpl runTemplate(final Map<String, Object> parameters) {
        try {
            return this.template.createContext(RenderMessageScriptContext.class).prepare(parameters).dispatchExecution();
        } catch (ScriptExecutorException e) {
            throw new HydraException(HydraMessages.VELOCITY_TEMPLATE.create(), e);
        }
    }
}